﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Passwords.Models;
using System.Diagnostics;
using Passwords.Properties;
using PasswordsService;
using System.Security;
using Passwords.DAL;
using System.Threading;

namespace Passwords.Services
{
    /// <summary>
    /// Self hosting sync service to  sync, backup and restore passwords from windows phone 7.
    /// </summary>
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Single)]
    public class SyncService : IPasswordSyncService
    {
        const int CurrentVersionId = 1000;

        #region fields

        private Thread hostingThread;
        private ServiceHost host;

        #endregion
        #region ctor

        public SyncService()
            : base()
        {
        }

        #endregion
        #region props

        private bool isFaulted;
        private string serviceError;

        public string ServiceError
        {
            get { return serviceError; }
            set
            {
                if (serviceError != value)
                {
                    serviceError = value;
                }
            }
        }


        public bool IsFaulted
        {
            get { return isFaulted; }
            set
            {
                if (isFaulted != value)
                {
                    isFaulted = value;
                }
            }
        }

        public bool IsOpened
        {
            get { return host != null && host.State == CommunicationState.Opened; }
        }

        protected ServiceHost Host
        {
            get { return host ?? (host = CreateHost()); }
        }

        protected SyncContext Context { get; private set; }

        /// <summary>
        /// Gets or sets the DataContext for the service. if DataContext is set, the service is enabled to sync (live).
        /// </summary>
        public IDataContext DataContext { get; set; }

        #endregion
        #region IPasswordSyncService

        public int Version()
        {
            return CurrentVersionId;
        }

        public string Ping(string value)
        {
            return DateTime.Now + ":" + value;
        }

        public bool LoginUnknown(Guid deviceId, Guid sessionId, SyncMode mode)
        {
            return LoginInternal(deviceId, sessionId, null, mode, false);
        }

        public bool Login(Guid deviceId, Guid sessionId, byte[] hash, SyncMode mode)
        {
            return LoginInternal(deviceId, sessionId, hash, mode, true);
        }

        private bool LoginInternal(Guid deviceId, Guid sessionId, byte[] hash, SyncMode mode, bool checkPassword)
        {
            // syncing is only possible when online:
            if (mode == SyncMode.Sync && DataContext == null) return false;

            SecureString password = null;
            if (checkPassword && mode == SyncMode.Sync)
            {

                password = DataContext.GetSyncPassword(deviceId, hash);
                if (password == null) return false;
                if (!DALUtil.IsEqual(hash, password.ComputeHash())) return false;
            }

            Context = SyncContext.Current;
            Context.DataContext = DataContext;
            Context.SyncMode = mode;
            Context.SetPassword(password);
            Context.PrepareSync(deviceId);

            if ((password != null) && (!password.IsReadOnly())) password.Clear();
            Context.RaiseStateChanged(ServiceState.Login);

            if (mode != SyncMode.Restore)
            {
                Backup backup = mode == SyncMode.Backup ? Context.CreateNewBackup(sessionId, deviceId) : Context.GetSyncBackup(sessionId, deviceId);
                backup.DeviceId = deviceId;
                backup.PasswordHash = hash;
                Context.Backup = backup;
            }

            return true;
        }


        public void Logout(Guid sessionId)
        {
            if (Context != null)
            {
                Context.RaiseStateChanged(ServiceState.Logout);
                if (Context.Backup != null)
                {
                    if (Context.SyncMode == SyncMode.Backup)
                    {
                        Context.SaveBackup(Context.Backup);
                    }
                    Context.Backup = null;
                }
                Context.Logout();
                Context.RaiseStateChanged(ServiceState.None);
                Context.Dispose();
                Context = null;
            }
        }

        public int GetPasswordCount(Guid sessionId)
        {
            if (Context == null) return 0;

            Context.RaiseStateChanged(ServiceState.GetPasswordCount);
            switch (Context.SyncMode)
            {
                case SyncMode.Backup:
                    return 0;

                default:
                    return Context.CountPasswords(sessionId);
            }
        }

        public PasswordContract GetPassword(Guid sessionId, int index)
        {
            if (Context == null) return null;
            Context.RaiseStateChanged(ServiceState.GetPassword);
            return Context.GetPassword(index);
        }

        public UpdateMode SendPassword(Guid sessionId, PasswordContract password)
        {
            if (Context == null) return UpdateMode.Timeout;
            Context.RaiseStateChanged(ServiceState.SendPassword);
            switch (Context.SyncMode)
            {
                case SyncMode.Restore:
                    // in case of Restore mode, the password which is currently on the device should be deleted:
                    return UpdateMode.Deleted;

                default:
                    return Context.SendPassword(password);

            }
        }

        public UpdateMode DeletePassword(Guid sessionId, PasswordContract password)
        {
            if (Context == null) return UpdateMode.Timeout;
            Context.RaiseStateChanged(ServiceState.DeletePassword);
            return Context.DeletePassword(password);

        }

        public BackupDataContract[] GetBackups(Guid deviceId, byte[] passwordHash)
        {
            SyncContext context = SyncContext.Current;
            {
                context.RaiseStateChanged(ServiceState.GetBackups);

                var result = context.Backups
                    .Where(b => passwordHash == null || BackupUtils.EqualsPassword(b.PasswordHash, passwordHash))
                    .Select(b => new BackupDataContract
                    {
                        Info = b.Info,
                        DateCreated = b.DateCreated,
                        Id = context.Backups.IndexOf(b),
                        Name = b.Name,
                        PasswordCount = b.Passwords.Count,
                        CategoryCount = b.Categories.Count,
                        Hash = passwordHash == null ? b.PasswordHash : null
                    }).ToArray();
                return result;
            }
        }

        public bool SelectBackup(Guid sessionId, int backupId)
        {
            if (Context == null) return false;

            if (Context.Backups.Count > backupId && backupId >= 0)
            {
                Context.Backup = Context.Backups[backupId];
                return true;
            }
            else
            {
                return false;
            }
        }

        public byte[] GetPasswordHash(Guid sessionId)
        {
            if (Context == null) return new byte[] { };
            return Context.GetPasswordHash();
        }


        public CategoryContract GetCategory(Guid sessionId, int index)
        {
            if (Context == null) return null;

            Context.RaiseStateChanged(ServiceState.GetCategory);
            return Context.GetCategory(index);

        }

        public int GetCategoryCount(Guid sessionId)
        {
            if (Context == null) return 0;

            Context.RaiseStateChanged(ServiceState.GetCategoryCount);
            switch (Context.SyncMode)
            {
                case SyncMode.Backup:
                    return 0;

                default:
                    return Context.CountCategories(sessionId);
            }
        }

        public UpdateMode SendCategory(Guid sessionId, CategoryContract category)
        {
            if (Context == null) return UpdateMode.Timeout;


            Context.RaiseStateChanged(ServiceState.SendCategory);
            switch (Context.SyncMode)
            {
                case SyncMode.Restore:
                    // in case of Restore mode, the category which is currently on the device should be deleted:
                    return UpdateMode.Deleted;

                default:
                    return Context.SendCategory(category);

            }
        }


        public UpdateMode DeleteCategory(Guid sessionId, CategoryContract category)
        {
            return UpdateMode.Rejected;
        }

        #endregion
        #region methods

        private ServiceHost CreateHost()
        {
            string uri = ServiceUtil.ServiceUri;
            ServiceHost host = new ServiceHost(this, new Uri(uri));
            // host.Faulted += new EventHandler(host_Faulted);
            host.UnknownMessageReceived += delegate { Trace.WriteLine("Unknown message received."); };
            return host;
        }

        void host_Faulted(object sender, EventArgs e)
        {
            IsFaulted = true;
            var eh = Faulted;
            if (eh != null) eh(this, e);
        }

        /// <summary>
        /// Opens the service hosted in a separate thread.
        /// This is necassary, as the sync services interacts with the UI by using wait handles to wait for the UI to complete when asking for the password of the phone to sync,
        /// otherwise this would lead to a deadlock.
        /// </summary>
        public void Open()
        {
            try
            {
                if (!IsOpened)
                {
                    IsFaulted = false;
                    ServiceError = "";
                    Thread t = new Thread((ParameterizedThreadStart)delegate
                        {
                            try
                            {
                                Host.Open();
                            }
                            catch (Exception e)
                            {
                                this.ServiceError = e.Message;
                                host_Faulted(this, EventArgs.Empty);
                                host = null;
                            }
                        });
                    t.Start();
                    hostingThread = t;
                }
            }
            catch (Exception e)
            {
                Trace.WriteLine(e.Message);
            }
        }

        /// <summary>
        /// Closes the service.
        /// </summary>
        public void Close()
        {
            ServiceError = "";
            if (IsOpened)
            {
                if (Host.State == CommunicationState.Opened)
                {
                    Host.Close();
                }
                host = null;
                hostingThread = null;
            }
        }

        #endregion
        #region events

        public event EventHandler Faulted;

        #endregion

    }
}
